<?php
// 应用公共文件

use app\common\exception\BusinessException;
use app\common\helper\SSEHelper;
use app\common\model\AlarmMessage;
use app\common\service\BdBceService;
use app\common\service\GlobalsVariableService;
use app\common\service\SphinxClient;
use PHPMailer\PHPMailer\PHPMailer;
use think\facade\Config;


/**
 * 编码转换
 * @param string $char
 * @param string $coding
 * @return string
 */
function convert_char(string $char, $coding = 'UTF-8'): string
{
    $encode_arr = ['UTF-8', 'ASCII', 'GBK', 'GB2312', 'BIG5', 'JIS', 'eucjp-win', 'sjis-win', 'EUC-JP'];
    $encoded = mb_detect_encoding($char, $encode_arr);
    if ($encoded) {
        $char = mb_convert_encoding($char, $coding, $encoded);
    }
    return $char;
}

/**
 * 发送邮件
 * @param $address
 * @param $subject
 * @param $message
 * @throws \PHPMailer\PHPMailer\Exception
 * @throws BusinessException
 */
function send_email($address, $subject, $message)
{
    $config = app(\app\common\model\Config::class)->getAll();


    $mail = new PHPMailer();
    $mail->IsSMTP();
    $mail->IsHTML(true);
    $mail->CharSet = 'UTF-8';
    $mail->AddAddress($address);
    $mail->Body = $message;
    $mail->From = $config['mail_smtp_user'];
    $mail->FromName = $config['mail_smtp_user'];
    $mail->Subject = $subject;
    $mail->Host = $config['mail_smtp_host'];
    $mail->SMTPSecure = $config['mail_smtp_pass'];
    $mail->Port = $config['mail_smtp_port'];;
    $mail->SMTPAuth = true;
    $mail->SMTPAutoTLS = true;
    $mail->Timeout = 10;
    $mail->Username = $config['mail_smtp_user'];
    $mail->Password = $config['mail_smtp_pass'];
    if (!$mail->Send()) {
        throw new BusinessException($mail->ErrorInfo);
    }
}

/**
 * 二维数组转字典
 * @param array $list
 * @param string $key
 * @return array
 */
function array_dict(array $list, string $key): array
{
    $dict = [];
    foreach ($list as $v) {
        if (!isset($v[$key])) {
            continue;
        }
        $dict[$v[$key]] = $v;
    }

    return $dict;
}

/**
 * array_cate
 * @param array $list
 * @param string $key
 * @return array
 */
function array_cate(array $list, string $key): array
{
    $cate = [];
    foreach ($list as $v) {
        $cate[$v['course_id']][] = $v;
    }
    return $cate;
}

/**
 * 通过Curl方式提交数据
 *
 * @param string $url 目标URL
 * @param null $data 提交的数据
 * @param bool $return_array 是否转成数组
 * @param null $header 请求头信息 如：array("Content-Type: application/json")
 * @param int $retryNum 重试次数
 *
 * @return array|mixed
 * @throws BusinessException
 */
function curl(string $url, $data = null, $return_array = false, $header = null, $retryNum = 1,$timeout = 30)
{
    // 初始化curl
    $curl = curl_init();

    // 设置超时
    curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_ENCODING, 'gzip, deflate');
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    if (is_array($header)) {
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    }
    if ($data) {
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
    }
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

    // 运行curl，获取结果
    $result = curlExecByRetry($curl, $retryNum);

    // 存在错误，抛出异常
    $errNo = curl_errno($curl);
    $error = curl_error($curl);
    if ($errNo) {
        throw new BusinessException('网络请求失败:' . $error);
    }

    // 关闭句柄
    curl_close($curl);

    // 转成数组
    if ($return_array) {
        return json_decode($result, true);
    }

    // 返回结果
    return $result;
}

/**
 * 避免Http请求响应为空时支持重试机制
 * @param $ch
 * @param int $retryNum
 * @return string
 */
function curlExecByRetry(
    $ch,
    int $retryNum = 1
): string
{
    while ($retryNum--) {
        $response = @curl_exec($ch);
        if ($response) {
            return $response;
        }
    }
    return '';
}

/**
 * 检查Json类型Api响应内容
 * @param string $name 响应内容
 * @param string $response 响应内容
 * @param array $fields 包含的字段
 * @return array
 * @throws BusinessException
 */
function checkJsonApiResponse(string $name, string $response, array $fields = []): array
{
    if (!$response) {
        throw new BusinessException("{$name}服务异常,未返回内容");
    }
    $response = json_decode($response, true);
    if (!$response) {
        throw new BusinessException("{$name}服务异常,响应内容非Json:" . $response);
    }
    foreach ($fields as $field) {
        if (!array_key_exists($field, $response)) {
            throw new BusinessException("{$name}服务响应内容未包含{$field}字段");
        }
    }
    return $response;
}

/**
 * ID生成器
 * @return string
 */
function idBuilder(): string
{
    static $i = 0;
    $i or $i = mt_rand(1, 0x7FFFFF);

    return sprintf(
        "%08x%06x%04x%06x",
        /* 4-byte value representing the seconds since the Unix epoch. */
        time() & 0xFFFFFFFF,

        /* 3-byte machine identifier.
         *
         * On windows, the max length is 256. Linux doesn't have a limit, but it
         * will fill in the first 256 chars of hostname even if the actual
         * hostname is longer.
         *
         * From the GNU manual:
         * gethostname stores the beginning of the host name in name even if the
         * host name won't entirely fit. For some purposes, a truncated host name
         * is good enough. If it is, you can ignore the error code.
         *
         * crc32 will be better than Times33. */
        crc32(substr((string)gethostname(), 0, 256)) >> 8 & 0xFFFFFF,

        /* 2-byte process id. */
        getmypid() & 0xFFFF,

        /* 3-byte counter, starting with a random value. */
        $i = $i > 0xFFFFFE ? 1 : $i + 1
    );
}

/**
 * formatTime
 * @param int $time
 * @param string $format
 * @param string $default
 * @return false|string
 */
function formatTime($time, $format = "Y-m-d H:i:s", $default = '-')
{
    if (!$time) {
        return $default;
    }
    $time = intval($time);
    return date($format, $time);
}

/**
 * beautifyTime
 * @param $time
 * @return string
 */
function beautifyTime($time): string
{
    // 当前时间
    $nowTime = time();

    // 时间差
    $diffTime = $nowTime - $time;

    // 输出文本
    $text = '刚刚';
    if ($diffTime == $time) {
        return $text;
    }

    // 时间是曾经/未来
    $text = $diffTime > 0 ? '前' : '后';

    // 求时间差绝对值
    $absDiffTime = abs($diffTime);

    // 根据时间差显示时间
    if ($absDiffTime <= 59) {
        return $absDiffTime . '秒' . $text;
    } elseif ($absDiffTime >= 60 && $absDiffTime < 3600) {
        $minute = floor($absDiffTime / 60);
        return $minute . '分钟' . $text;
    } elseif ($absDiffTime >= 3600 && $absDiffTime < 86400) {
        $hour = floor($absDiffTime / 3600);
        return $hour . '小时' . $text;
    } else {
        return date('Y-m-d', $time);
    }
}

// 获取毫秒时间戳
function getMicrosecondTimestamp()
{
    [$msec, $sec] = explode(' ', microtime());
    $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    return $msectime;
}

/**
 * 生成二维码Url
 * @param $wxUrl
 * @return string
 */
function buildQrCodeUrl($wxUrl): string
{
    $appConfig = Config::get('app');
    return $appConfig['app_host'] . '/api/qrCode/show?url=' . $wxUrl;
}

/**
 * getSphinxClient
 * @return SphinxClient
 */
function getSphinxClient(): SphinxClient
{
    $config = Config::get('sphinx');
    $cl = new SphinxClient ();
    $cl->SetServer($config['host'], $config['port']);
    $cl->SetConnectTimeout(1);
    return $cl;
}

/**
 * hideMobile
 * @param $mobile
 * @return string
 */
function hideMobile($mobile): string
{
    if (strlen($mobile) !== 11) {
        return $mobile;
    }
    return substr_replace($mobile, '****', 3, 4);
}

/**
 * addAlarmMessage
 * @param $title
 * @param $content
 * @return void
 * @throws BusinessException
 */
function addAlarmMessage($title, $content)
{
    $model = new AlarmMessage();
    $time = time();
    $saveData = [
        'title' => $title,
        'content' => $content,
        'status' => 1,
        'create_time' => $time,
        'update_time' => $time,
    ];
    if (!$model->add_data($saveData)) {
        throw new BusinessException('保存警告信息失败');
    }
}

/**
 * 保留小数(截断形式)
 * @param $number
 * @param int $digit
 * @return float|int
 */
function truncateDecimal($number, int $digit = 2)
{
    $multiplier = pow(10, $digit);
    return floor($number * $multiplier) / $multiplier;
}

/**
 * createOrderNo
 * @param string $prefix
 * @return string
 */
function createOrderNo(string $prefix = 'S'): string
{
    return $prefix . date('Ymd') . time() . rand(111, 999);
}

/**
 * 通过Curl方式提交数据
 *
 * @param string $url 目标URL
 * @param null $data 提交的数据
 * @param null $header 请求头信息 如：array("Content-Type: application/json")
 *
 * @return array|mixed
 * @throws Exception
 */
function curlResponse($url, $data = null, $header = null, $retryNum = 3)
{
    // 初始化curl
    $curl = curl_init();

    // 设置超时
    curl_setopt($curl, CURLOPT_TIMEOUT, 30);
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_HEADER, 1);
    if (is_array($header)) {
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    }
    if ($data) {
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
    }
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

    // 运行curl，获取结果
    $data = curlExecByRetry($curl, $retryNum);

    // 定义返回信息
    $response = [];

    // 根据头大小去获取头信息内容
    if ($data) {
        $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $header = substr($data, 0, $headerSize);
        $response['responseHeader'] = $header;
        $response['responseBody'] = substr($data, $headerSize);
    }

    // 关闭句柄
    curl_close($curl);

    // 返回结果
    return $response;
}


/**
 * getCookiesFromResponseHeader
 * @param $responseHeader
 * @return mixed|string
 */
function getCookiesFromResponseHeader($responseHeader)
{
    $cookies = "";
    $allHeader = explode("\r\n", $responseHeader);
    foreach ($allHeader as $header) {
        $headerNode = explode(':', $header);
        $headerKey = $headerNode['0'] ?? '';
        $headerVal = $headerNode['1'] ?? '';
        if (!$headerKey || !$headerVal || $headerKey != 'Set-Cookie') {
            continue;
        }
        $cookies = $headerVal;
        break;
    }
    return $cookies;
}

/**
 * 获取Url中的Host信息
 * @param $url
 * @return mixed|string
 */
function getUrlHost($url)
{
    $parse_data = parse_url($url);
    if (!empty($parse_data['host'])) {
        return $parse_data['host'];
    }
    return '';
}

/**
 * getUrlFullHost
 * @param $url
 * @return string
 */
function getUrlFullHost($url): string
{
    $parse_data = parse_url($url);
    $host = '';
    if (empty($parse_data['scheme'])) {
        return '';
    }
    $host .= $parse_data['scheme'];
    if (empty($parse_data['host'])) {
        return '';
    }
    return $host . '://' . $parse_data['host'];
}

/**
 * 获取中文首字母
 * @param $char
 * @return string
 */
function getChineseFirstLetter($char): string
{
    $fchar = ord($char[0]);
    if ($fchar >= ord("A") and $fchar <= ord("z")) return strtoupper($char[0]);
    $s1 = iconv("UTF-8", "gb2312", $char);
    $s2 = iconv("gb2312", "UTF-8", $s1);
    if ($s2 == $char) {
        $s = $s1;
    } else {
        $s = $char;
    }
    $asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
    if ($asc >= -20319 and $asc <= -20284) return "A";
    if ($asc >= -20283 and $asc <= -19776) return "B";
    if ($asc >= -19775 and $asc <= -19219) return "C";
    if ($asc >= -19218 and $asc <= -18711) return "D";
    if ($asc >= -18710 and $asc <= -18527) return "E";
    if ($asc >= -18526 and $asc <= -18240) return "F";
    if ($asc >= -18239 and $asc <= -17923) return "G";
    if ($asc >= -17922 and $asc <= -17418) return "H";
    if ($asc >= -17922 and $asc <= -17418) return "I";
    if ($asc >= -17417 and $asc <= -16475) return "J";
    if ($asc >= -16474 and $asc <= -16213) return "K";
    if ($asc >= -16212 and $asc <= -15641) return "L";
    if ($asc >= -15640 and $asc <= -15166) return "M";
    if ($asc >= -15165 and $asc <= -14923) return "N";
    if ($asc >= -14922 and $asc <= -14915) return "O";
    if ($asc >= -14914 and $asc <= -14631) return "P";
    if ($asc >= -14630 and $asc <= -14150) return "Q";
    if ($asc >= -14149 and $asc <= -14091) return "R";
    if ($asc >= -14090 and $asc <= -13319) return "S";
    if ($asc >= -13318 and $asc <= -12839) return "T";
    if ($asc >= -12838 and $asc <= -12557) return "W";
    if ($asc >= -12556 and $asc <= -11848) return "X";
    if ($asc >= -11847 and $asc <= -11056) return "Y";
    if ($asc >= -11055 and $asc <= -10247) return "Z";
    return "";
}

/**
 * getAppId
 * @return false|mixed
 */
function getAppId()
{
    return GlobalsVariableService::get('appId') ?: 1000;
}

/**
 * getBdBceService
 * @param $key
 * @return BdBceService
 */
function getBdBceService($key): BdBceService
{
    $config = config('bce.' . $key);
    return new BdBceService($config['host'], $config['client_id'], $config['client_secret']);
}

/**
 * 通过Curl+Stream方式提交数据
 *
 * @param string $url
 * @param null $header
 * @param null $data
 * @param Closure $closure
 * @throws
 */
function curlWithStream(string $url, $header = null, $data = null, $closure)
{
    if (!($closure instanceof Closure)) {
        throw new \think\Exception('closure must instanceof Closure');
    }
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_FORBID_REUSE, false);
    curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
    curl_setopt($ch, CURLOPT_TCP_KEEPIDLE, 120);
    curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $str) use ($closure) {
        return $closure($ch, $str);
    });
    curl_exec($ch);
    curl_close($ch);
}

/**
 * rsaEncrypt
 * @throws BusinessException
 */
function rsaEncrypt($inputData, $encryptRsaKey, $isPublic = true): string
{
    $encryptRes = null;
    $encryptStatus = $isPublic ? openssl_public_encrypt($inputData, $encryptRes, $encryptRsaKey) : openssl_private_encrypt($inputData, $encryptRes, $encryptRsaKey);
    if (!$encryptStatus) {
        throw new BusinessException('rsa加密失败');
    }
    return base64_encode($encryptRes);
}


/**
 * getEsClient(es versions 8.15.0)
 * @throws \Elastic\Elasticsearch\Exception\AuthenticationException
 */
function getEsClient(): \Elastic\Elasticsearch\Client
{
    $esConfig = Config::get('es');
    return Elastic\Elasticsearch\ClientBuilder::create()
        ->setSSLVerification(false)
        ->setHosts($esConfig['hosts'])->setBasicAuthentication($esConfig['username'], $esConfig['password'])->build();
}

/**
 * getEsIndex
 * @param $index
 * @return string
 */
function getEsIndex($index): string
{
    $esConfig = Config::get('es');
    return $esConfig['prefix'] . $index;
}

/**
 * buildSqlOrderByField
 * @param $field
 * @param $values
 * @return string
 */
function buildSqlOrderByField($field, $values): string
{
    return 'FIELD(' . $field . ',' . join(',', $values) . ')';
}

/**
 * removeChineseCharacters
 * @param $str
 * @return array|string|string[]|null
 */
function removeChineseCharacters($str)
{
    return preg_replace('/[\x{4e00}-\x{9fa5}]/u', '', $str);
}